<template>
  <div class="tone-mapping">
    <h1>色调映射</h1>
    <p>这个示例展示了WebGL中的色调映射后期处理效果。色调映射用于将高动态范围(HDR)图像转换为低动态范围(LDR)显示设备可以显示的图像。</p>

    <el-card class="demo-section">
      <template #header>
        <div class="card-header">
          <h3>色调映射演示</h3>
        </div>
      </template>
      <div class="demo-content">
        <canvas ref="demoCanvas" width="400" height="300"></canvas>
        <div class="control-panel">
          <el-form :model="toneForm" label-width="120px">
            <el-form-item label="映射算法">
              <el-select v-model="toneForm.algorithm" @change="updateToneMapping">
                <el-option label="Reinhard" value="reinhard" />
                <el-option label="ACES" value="aces" />
                <el-option label="Uncharted 2" value="uncharted2" />
                <el-option label="线性" value="linear" />
              </el-select>
            </el-form-item>
            <el-form-item label="曝光度">
              <el-slider
                v-model="toneForm.exposure"
                :min="0.1"
                :max="5.0"
                :step="0.1"
                @input="updateToneMapping"
              />
            </el-form-item>
            <el-form-item label="白点">
              <el-slider
                v-model="toneForm.whitePoint"
                :min="0.5"
                :max="10.0"
                :step="0.1"
                @input="updateToneMapping"
              />
            </el-form-item>
            <el-form-item label="伽马校正">
              <el-slider
                v-model="toneForm.gamma"
                :min="1.0"
                :max="3.0"
                :step="0.1"
                @input="updateToneMapping"
              />
            </el-form-item>
            <el-form-item label="饱和度">
              <el-slider
                v-model="toneForm.saturation"
                :min="0.0"
                :max="2.0"
                :step="0.1"
                @input="updateToneMapping"
              />
            </el-form-item>
            <el-form-item label="启用色调映射">
              <el-switch v-model="toneForm.enableToneMapping" @change="updateToneMapping" />
            </el-form-item>
          </el-form>
          <p>这是一个色调映射后期处理效果，你可以：</p>
          <ul>
            <li>选择不同的色调映射算法</li>
            <li>调整曝光度和白点</li>
            <li>修改伽马校正值</li>
            <li>控制图像饱和度</li>
          </ul>
        </div>
      </div>
    </el-card>

    <el-card class="code-section">
      <template #header>
        <div class="card-header">
          <h3>示例代码</h3>
        </div>
      </template>
      <div class="code-content">
        <pre><code>
// 顶点着色器
{{ VSHADER_SOURCE }}

// 片元着色器
{{ FSHADER_SOURCE }}
        </code></pre>
      </div>
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, reactive, onUnmounted } from 'vue'
import { WebGLUtils } from '@/utils/webgl'

const demoCanvas = ref<HTMLCanvasElement | null>(null)
let gl: WebGLRenderingContext | null = null
let program: WebGLProgram | null = null
let webglUtils: WebGLUtils | null = null
let animationId: number | null = null
let texture: WebGLTexture | null = null

const toneForm = reactive({
  algorithm: 'reinhard',
  exposure: 1.0,
  whitePoint: 2.0,
  gamma: 2.2,
  saturation: 1.0,
  enableToneMapping: true
})

// 顶点着色器
const VSHADER_SOURCE = `
  attribute vec4 a_Position;
  attribute vec2 a_TexCoord;
  varying vec2 v_TexCoord;
  void main() {
    gl_Position = a_Position;
    v_TexCoord = a_TexCoord;
  }
`

// 片元着色器
const FSHADER_SOURCE = `
  precision mediump float;
  uniform sampler2D u_Texture;
  uniform int u_Algorithm; // 0: reinhard, 1: aces, 2: uncharted2, 3: linear
  uniform float u_Exposure;
  uniform float u_WhitePoint;
  uniform float u_Gamma;
  uniform float u_Saturation;
  uniform bool u_EnableToneMapping;
  varying vec2 v_TexCoord;

  // ACES色调映射
  vec3 ACESFilm(vec3 x) {
    float a = 2.51;
    float b = 0.03;
    float c = 2.43;
    float d = 0.59;
    float e = 0.14;
    return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
  }

  // Uncharted 2色调映射
  vec3 Uncharted2Tonemap(vec3 x) {
    float A = 0.15;
    float B = 0.50;
    float C = 0.10;
    float D = 0.20;
    float E = 0.02;
    float F = 0.30;
    return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
  }

  // Reinhard色调映射
  vec3 ReinhardTonemap(vec3 color, float whitePoint) {
    return color * (1.0 + color / (whitePoint * whitePoint)) / (1.0 + color);
  }

  // 线性色调映射
  vec3 LinearTonemap(vec3 color) {
    return clamp(color, 0.0, 1.0);
  }

  // 调整饱和度
  vec3 adjustSaturation(vec3 color, float saturation) {
    float luminance = dot(color, vec3(0.299, 0.587, 0.114));
    return mix(vec3(luminance), color, saturation);
  }

  void main() {
    vec3 color = texture2D(u_Texture, v_TexCoord).rgb;

    if (!u_EnableToneMapping) {
      gl_FragColor = vec4(color, 1.0);
      return;
    }

    // 应用曝光
    color *= u_Exposure;

    // 应用色调映射
    if (u_Algorithm == 0) {
      // Reinhard
      color = ReinhardTonemap(color, u_WhitePoint);
    } else if (u_Algorithm == 1) {
      // ACES
      color = ACESFilm(color);
    } else if (u_Algorithm == 2) {
      // Uncharted 2
      color = Uncharted2Tonemap(color);
      vec3 whiteScale = 1.0 / Uncharted2Tonemap(vec3(u_WhitePoint));
      color *= whiteScale;
    } else if (u_Algorithm == 3) {
      // Linear
      color = LinearTonemap(color);
    }

    // 调整饱和度
    color = adjustSaturation(color, u_Saturation);

    // 伽马校正
    color = pow(color, vec3(1.0 / u_Gamma));

    gl_FragColor = vec4(color, 1.0);
  }
`

function initVertexBuffers(gl: WebGLRenderingContext): boolean {
  // 全屏四边形
  const vertices = new Float32Array([
    -1.0, -1.0, 0.0, 0.0,
     1.0, -1.0, 1.0, 0.0,
    -1.0,  1.0, 0.0, 1.0,
     1.0,  1.0, 1.0, 1.0
  ])

  const buffer = gl.createBuffer()
  if (!buffer) {
    console.error('Failed to create buffer')
    return false
  }

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)

  const a_Position = gl.getAttribLocation(program!, 'a_Position')
  const a_TexCoord = gl.getAttribLocation(program!, 'a_TexCoord')

  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 16, 0)
  gl.enableVertexAttribArray(a_Position)

  gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, 16, 8)
  gl.enableVertexAttribArray(a_TexCoord)

  return true
}

function createTexture(gl: WebGLRenderingContext): WebGLTexture | null {
  const texture = gl.createTexture()
  if (!texture) return null

  // 创建一个高对比度的HDR风格图像
  const canvas = document.createElement('canvas')
  canvas.width = 256
  canvas.height = 256
  const ctx = canvas.getContext('2d')!

  // 创建径向渐变模拟HDR效果
  const gradient = ctx.createRadialGradient(128, 128, 0, 128, 128, 128)
  gradient.addColorStop(0, '#ffffff')  // 非常亮的中心
  gradient.addColorStop(0.2, '#ffff88')
  gradient.addColorStop(0.4, '#ff8844')
  gradient.addColorStop(0.6, '#ff4444')
  gradient.addColorStop(0.8, '#884444')
  gradient.addColorStop(1, '#000000')

  ctx.fillStyle = gradient
  ctx.fillRect(0, 0, 256, 256)

  // 添加一些高亮区域
  ctx.fillStyle = '#ffffff'
  ctx.globalAlpha = 0.8
  ctx.beginPath()
  ctx.arc(80, 80, 20, 0, Math.PI * 2)
  ctx.fill()

  ctx.beginPath()
  ctx.arc(180, 60, 15, 0, Math.PI * 2)
  ctx.fill()

  ctx.beginPath()
  ctx.arc(200, 180, 25, 0, Math.PI * 2)
  ctx.fill()

  // 添加一些暗部细节
  ctx.globalAlpha = 0.3
  ctx.fillStyle = '#444444'
  ctx.fillRect(20, 200, 60, 40)
  ctx.fillRect(150, 220, 80, 30)

  ctx.globalAlpha = 1.0

  gl.bindTexture(gl.TEXTURE_2D, texture)
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas)
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)

  return texture
}

function updateToneMapping() {
  if (!gl || !program) return

  const u_Algorithm = gl.getUniformLocation(program, 'u_Algorithm')
  const u_Exposure = gl.getUniformLocation(program, 'u_Exposure')
  const u_WhitePoint = gl.getUniformLocation(program, 'u_WhitePoint')
  const u_Gamma = gl.getUniformLocation(program, 'u_Gamma')
  const u_Saturation = gl.getUniformLocation(program, 'u_Saturation')
  const u_EnableToneMapping = gl.getUniformLocation(program, 'u_EnableToneMapping')

  const algorithmMap: { [key: string]: number } = {
    'reinhard': 0,
    'aces': 1,
    'uncharted2': 2,
    'linear': 3
  }

  gl.uniform1i(u_Algorithm, algorithmMap[toneForm.algorithm])
  gl.uniform1f(u_Exposure, toneForm.exposure)
  gl.uniform1f(u_WhitePoint, toneForm.whitePoint)
  gl.uniform1f(u_Gamma, toneForm.gamma)
  gl.uniform1f(u_Saturation, toneForm.saturation)
  gl.uniform1i(u_EnableToneMapping, toneForm.enableToneMapping ? 1 : 0)

  draw()
}

function draw() {
  if (!gl || !program) return

  gl.clear(gl.COLOR_BUFFER_BIT)
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
}

function animate() {
  draw()
  animationId = requestAnimationFrame(animate)
}

function init() {
  if (!demoCanvas.value) return

  gl = demoCanvas.value.getContext('webgl')
  if (!gl) {
    console.error('WebGL not supported')
    return
  }

  webglUtils = new WebGLUtils(gl)
  // 创建着色器
  const vertexShader = webglUtils.createShader(gl.VERTEX_SHADER, VSHADER_SOURCE)
  const fragmentShader = webglUtils.createShader(gl.FRAGMENT_SHADER, FSHADER_SOURCE)
  if (!vertexShader || !fragmentShader) return
  program = webglUtils.createProgram(vertexShader, fragmentShader)
  if (!program) {
    console.error('Failed to create program')
    return
  }

  gl.useProgram(program)

  if (!initVertexBuffers(gl)) {
    console.error('Failed to initialize vertex buffers')
    return
  }

  texture = createTexture(gl)
  if (!texture) {
    console.error('Failed to create texture')
    return
  }

  // 设置uniform变量
  const u_Texture = gl.getUniformLocation(program, 'u_Texture')

  gl.uniform1i(u_Texture, 0)

  gl.activeTexture(gl.TEXTURE0)
  gl.bindTexture(gl.TEXTURE_2D, texture)

  gl.clearColor(0.0, 0.0, 0.0, 1.0)
  gl.viewport(0, 0, demoCanvas.value.width, demoCanvas.value.height)

  updateToneMapping()
  animate()
}

onMounted(() => {
  init()
})

onUnmounted(() => {
  if (animationId) {
    cancelAnimationFrame(animationId)
  }
})
</script>

<style scoped>
.tone-mapping {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
}

h1 {
  color: #303133;
  margin-bottom: 20px;
}

p {
  line-height: 1.6;
  color: #606266;
  margin-bottom: 30px;
}

.demo-section {
  margin-bottom: 30px;
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.demo-content {
  display: flex;
  gap: 30px;
  align-items: flex-start;
}

canvas {
  border: 1px solid #dcdfe6;
  border-radius: 4px;
}

.control-panel {
  flex: 1;
  min-width: 300px;
}

.control-panel p {
  margin: 20px 0 10px;
  font-weight: bold;
  color: #409EFF;
}

.control-panel ul {
  list-style-type: disc;
  padding-left: 20px;
  color: #606266;
}

.control-panel li {
  margin: 5px 0;
}

.code-section {
  margin-top: 30px;
}

.code-content {
  background-color: #f5f7fa;
  border-radius: 4px;
  overflow-x: auto;
}

.code-content pre {
  margin: 0;
  padding: 20px;
  font-family: 'Courier New', monospace;
  font-size: 14px;
  line-height: 1.5;
  color: #303133;
}
</style>
